﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.IO;
using System.Net;
using System.Xml;
using System.Xml.Linq;
using ServiceStack.Logging;
using ServiceStack.Configuration;
using ServiceStack.Text;
using System.Collections.Specialized;
using ServiceStack.ServiceHost;


namespace DocuSignPlugin
{
    public class DocuSignAPIs
    {

        private static readonly ILog _log = LogManager.GetLogger(typeof(DocuSignAPIs));

        private string _key = "";
        private string _url = "";
        private string _returnUrl;


        string contentType = "application/pdf";

        public DocuSignAPIs(AppSettings appSettings)
        {
            _key = appSettings.GetString("docusign.IntegratorKey");
            _url = appSettings.GetString("docusign.Url");
            _returnUrl = appSettings.GetString("docusign.ReturnUrl");
        }


        public void getDocusignDetails(LoginDetails loginDetails)
        {

            try
            {
                HttpWebRequest request = initializeRequest(_url + "login_information", "GET", null, loginDetails, _key);

                string response = getResponseBody(request);
                loginDetails.AccountId = parseDataFromResponse(response, "accountId");
                loginDetails.BaseUrl = parseDataFromResponse(response, "baseUrl");

            }
            catch (WebException e)
            {
                handleWebWexception("getDocusignDetails", e);
            }


        }

        public LoginDetails login(string username, string password)
        {

            LoginDetails loginDetails = new LoginDetails();
            try
            {
                WebClient webClient = new WebClient();
                webClient.Headers.Add(HttpRequestHeader.Accept, "application/json");
                webClient.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded");

                string token = webClient.UploadString(_url + "oauth2/token", string.Format("grant_type=password&client_id={0}&username={1}&password={2}&scope=api", _key, username, password));

                JsonObject jobject = JsonObject.Parse(token);

                loginDetails.AccessToken = jobject.Get("access_token");

            }
            catch (WebException e)
            {
                handleWebWexception("Login", e);
            }

            getDocusignDetails(loginDetails);

            return loginDetails;
        }

        private void handleWebWexception(string method, WebException e)
        {
            using (WebResponse response = e.Response)
            {
                HttpWebResponse httpResponse = (HttpWebResponse)response;
                _log.ErrorFormat("DocuSign - {0}: {1}", method, httpResponse.StatusCode);


                using (Stream data = response.GetResponseStream())
                {
                    string text = new StreamReader(data).ReadToEnd();

                    if (httpResponse.StatusCode == HttpStatusCode.Unauthorized)
                    {
                        throw new AuthenticationException(getErrorMessage(text));
                    }

                    throw new Exception(getErrorMessage(text));
                }
            }
        }


        public string getEnvelopeGUID(LoginDetails loginDetails, string docfullpath, string documentName, string recipientEmail, string recipientName)
        {
            string uri = null;
            //string key = Properties.Settings.Default.IntegratorKey;
            //_log.Debug("[INFO - DocuSignAPI] DocuSign -getEnvelopeGUID - username:  " + username);
            //_log.Debug("[INFO - DocuSignAPI] DocuSign -getEnvelopeGUID - password: " + password);
            //_log.Debug("[INFO - DocuSignAPI] DocuSign -getEnvelopeGUID - baseURL: " + baseURL);
            _log.Debug("[INFO - DocuSignAPI] DocuSign -getEnvelopeGUID - docfullpath: " + docfullpath);
            _log.Debug("[INFO - DocuSignAPI] DocuSign -getEnvelopeGUID - documentName: " + documentName);
            _log.Debug("[INFO - DocuSignAPI] DocuSign -getEnvelopeGUID - recipientEmail: " + recipientEmail);
            _log.Debug("[INFO - DocuSignAPI] DocuSign -getEnvelopeGUID - recipientName: " + recipientName);
            string url = loginDetails.BaseUrl + "/envelopes";
            string xmlBody =
                    "<envelopeDefinition xmlns=\"http://www.docusign.com/restapi\">" +
                    "<emailSubject>DocuSign API - Signature Request on Document</emailSubject>" +
                    "<status>sent</status>" + 	// "sent" to send immediately, "created" to save as draft in your account
                // add document(s)
                    "<documents>" +
                    "<document>" +
                    "<documentId>1</documentId>" +
                    "<name>" + documentName + "</name>" +
                    "</document>" +
                    "</documents>" +
                // add recipient(s)
                    "<recipients>" +
                    "<signers>" +
                    "<signer>" +
                    "<clientUserId>123</clientUserId>" +
                    "<recipientId>1</recipientId>" +
                    "<email>" + recipientEmail + "</email>" +
                    "<name>" + recipientName + "</name>" +
                    "</signer>" +
                    "</signers>" +
                    "</recipients>" +
                    "</envelopeDefinition>";
            try
            {
                HttpWebRequest request = initializeRequest(url, "POST", null, loginDetails, _key);

                // some extra config for this api call
                configureMultiPartFormDataRequest(request, xmlBody, docfullpath, documentName, contentType);

                // read the http response
                string response = getResponseBody(request);

                // parse the envelope uri from response body
                uri = parseDataFromResponse(response, "uri");
            }
            catch (WebException e)
            {

                handleWebWexception("getEnvelopeGUID", e);

                //    using (WebResponse response = e.Response)
                //    {
                //        HttpWebResponse httpResponse = (HttpWebResponse)response;
                //        _log.Debug("[ERROR - DocuSignAPI] DocuSign -getEnvelopeGUID: " + httpResponse.StatusCode);
                //        GoToErrorPage("Error code: {0}" + httpResponse.StatusCode);

                //        using (Stream data = response.GetResponseStream())
                //        {
                //            string text = new StreamReader(data).ReadToEnd();
                //            GoToErrorPage(prettyPrintXml(text));
                //        }
                //    }
            }


            return uri;

        }

        public string getViewURL(LoginDetails loginDetails, string uri, string recipientEmail, string recipientName, long recordUri, IHttpRequest httpRequest)
        {
            string url = null;
            //string key = Properties.Settings.Default.IntegratorKey;
            //string rUrl = Properties.Settings.Default.returnedUrl;

            //_log.Debug("[INFO - DocuSignAPI] DocuSign -getViewURL - username:  " + username);
            //_log.Debug("[INFO - DocuSignAPI] DocuSign -getViewURL - password: " + password);
            //_log.Debug("[INFO - DocuSignAPI] DocuSign -getViewURL - baseURL: " + baseURL);
            _log.Debug("[INFO - DocuSignAPI] DocuSign -getViewURL - uri: " + uri);

            _log.Debug("[INFO - DocuSignAPI] DocuSign -getViewURL - recipientEmail: " + recipientEmail);
            _log.Debug("[INFO - DocuSignAPI] DocuSign -getViewURL - recipientName: " + recipientName);

            url = loginDetails.BaseUrl + uri + "/views/recipient";

            string requestBody = "<recipientViewRequest xmlns=\"http://www.docusign.com/restapi\">" +
                    "<authenticationMethod>email</authenticationMethod>" +
                    "<email>" + recipientEmail + "</email>" +
                    "<returnUrl>" + httpRequest.Absolute(string.Format("~/SigningComplete?uri={0}", recordUri)) + "</returnUrl>" +
                    "<clientUserId>123</clientUserId>" +
                    "<userName>" + recipientName + "</userName>" +
                    "</recipientViewRequest>";

            //try
            //{
            HttpWebRequest request = initializeRequest(url, "POST", requestBody, loginDetails, _key);

            // read the http response
            string response = getResponseBody(request);

            // parse the envelope uri from response body
            url = parseDataFromResponse(response, "url");
            //}
            //catch (WebException e)
            //{
            //    using (WebResponse response = e.Response)
            //    {
            //        HttpWebResponse httpResponse = (HttpWebResponse)response;
            //        _log.Debug("[ERROR - DocuSignAPI] getEnvelopeGUID: " + httpResponse.StatusCode);
            //        GoToErrorPage("Error code: {0}" + httpResponse.StatusCode);

            //        using (Stream data = response.GetResponseStream())
            //        {
            //            string text = new StreamReader(data).ReadToEnd();
            //            GoToErrorPage(prettyPrintXml(text));
            //        }
            //    }
            //}

            return url;
        }

        public string getDocument(LoginDetails loginDetails, string envGuid, string location, string docName, string docExt)
        {
            //   string key = Properties.Settings.Default.IntegratorKey;
            //_log.Debug("[INFO - DocuSignAPI] DocuSign -getViewURL - username:  " + username);
            //_log.Debug("[INFO - DocuSignAPI] DocuSign -getViewURL - password: " + password);
            _log.Debug("[INFO - DocuSignAPI] DocuSign -getViewURL - envGuid: " + envGuid);
            _log.Debug("[INFO - DocuSignAPI] DocuSign -getViewURL - location: " + location);

            _log.Debug("[INFO - DocuSignAPI] DocuSign -getViewURL - docName: " + docName);
            _log.Debug("[INFO - DocuSignAPI] DocuSign -getViewURL - docExt: " + docExt);
            //try
            //{
            string url = loginDetails.BaseUrl + envGuid + "/documents/1";

            string fileLocation = Path.ChangeExtension(location + "\\" + docName, docExt);

            HttpWebRequest request = initializeRequest(url, "GET", null, loginDetails, _key);
            request.Accept = "application/pdf";

            HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse();

            using (MemoryStream ms = new MemoryStream())
            {
                using (FileStream outfile = new FileStream(fileLocation, FileMode.Create))
                {
                    webResponse.GetResponseStream().CopyTo(ms);
                    if (ms.Length > int.MaxValue)
                    {
                        throw new NotSupportedException("Cannot write a file larger than 2GB.");
                    }
                    outfile.Write(ms.GetBuffer(), 0, (int)ms.Length);
                }
            }

            return fileLocation;
            //}
            //catch (WebException e)
            //{
            //    using (WebResponse response = e.Response)
            //    {
            //        HttpWebResponse httpResponse = (HttpWebResponse)response;
            //        _log.Debug("[ERROR - DocuSignAPI] getDocument: " + httpResponse.StatusCode);
            //        GoToErrorPage("Error code: {0}" + httpResponse.StatusCode);
            //        using (Stream data = response.GetResponseStream())
            //        {
            //            string text = new StreamReader(data).ReadToEnd();
            //            GoToErrorPage(prettyPrintXml(text));
            //        }
            //    }
            //}
        }


        private HttpWebRequest createRequest(string url, string method, string body, NameValueCollection authHeader)
        {
            HttpWebRequest request = null;

            request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = method;
            request.Accept = "application/xml";
            request.ContentType = "application/xml";
            request.Headers.Add(authHeader);
            if (body != null)
            {
                addRequestBody(request, body);
            }
            return request;
        }
        public HttpWebRequest initializeRequest(string url, string method, string body, string email, string password, string intKey)
        {
            return createRequest(url, method, body, getUserNameHeader(email, password, intKey));
        }


        public HttpWebRequest initializeRequest(string url, string method, string body, LoginDetails loginDetails, string intKey)
        {
            return createRequest(url, method, body, getTokenHeader(loginDetails));
        }

        public NameValueCollection getTokenHeader(LoginDetails loginDetails)
        {
            NameValueCollection tokenHeader = new NameValueCollection();
            tokenHeader.Add("Authorization", string.Format("bearer {0}", loginDetails.AccessToken));

            return tokenHeader;

        }

        public NameValueCollection getUserNameHeader(string email, string password, string intKey)
        {

            NameValueCollection tokenHeader = new NameValueCollection();
            string authenticateStr =
                "<DocuSignCredentials>" +
                    "<Username>" + email + "</Username>" +
                    "<Password>" + password + "</Password>" +
                    "<IntegratorKey>" + intKey + "</IntegratorKey>" +
                    "</DocuSignCredentials>";
            tokenHeader.Add("X-DocuSign-Authentication", authenticateStr);

            return tokenHeader;

        }

        public void addRequestBody(HttpWebRequest request, string requestBody)
        {
            //try
            //{
            byte[] body = System.Text.Encoding.UTF8.GetBytes(requestBody);
            Stream dataStream = request.GetRequestStream();
            dataStream.Write(body, 0, requestBody.Length);
            dataStream.Close();
            //}
            //catch (WebException e)
            //{
            //    using (WebResponse response = e.Response)
            //    {
            //        HttpWebResponse httpResponse = (HttpWebResponse)response;
            //        _log.Debug("[ERROR - DocuSignAPI] addRequestBody: " + httpResponse.StatusCode);
            //        GoToErrorPage("Error code: {0}" + httpResponse.StatusCode);
            //        using (Stream data = response.GetResponseStream())
            //        {
            //            string text = new StreamReader(data).ReadToEnd();
            //            GoToErrorPage(prettyPrintXml(text));
            //        }
            //    }
            //}
        }

        public string getResponseBody(HttpWebRequest request)
        {
            string responseText = null;

            // try
            // {
            HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse();
            StreamReader sr = new StreamReader(webResponse.GetResponseStream());
            responseText = sr.ReadToEnd();
            //}
            //catch (WebException e)
            //{
            //    using (WebResponse response = e.Response)
            //    {
            //        HttpWebResponse httpResponse = (HttpWebResponse)response;
            //        _log.Debug("[ERROR - DocuSignAPI] getResponseBody: " + httpResponse.StatusCode);
            //        GoToErrorPage("Error code: {0}" + httpResponse.StatusCode);
            //        using (Stream data = response.GetResponseStream())
            //        {
            //            string text = new StreamReader(data).ReadToEnd();
            //            GoToErrorPage(prettyPrintXml(text));
            //        }
            //    }
            //}

            return responseText;
        }

        public string parseDataFromResponse(string response, string searchToken)
        {
            //try
            //{
            using (XmlReader reader = XmlReader.Create(new StringReader(response)))
            {
                while (reader.Read())
                {
                    if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == searchToken))
                        return reader.ReadString();
                }
            }
            //}
            //catch (WebException e)
            //{
            //    using (WebResponse res = e.Response)
            //    {
            //        HttpWebResponse httpResponse = (HttpWebResponse)res;
            //        _log.Debug("[ERROR - DocuSignAPI] parseDataFromResponse: " + httpResponse.StatusCode);
            //        GoToErrorPage("Error code: {0}" + httpResponse.StatusCode);
            //        using (Stream data = res.GetResponseStream())
            //        {
            //            string text = new StreamReader(data).ReadToEnd();
            //            GoToErrorPage(prettyPrintXml(text));
            //        }
            //    }
            //}

            return null;
        }

        public string getErrorMessage(string xml)
        {
            // print nicely formatted xml
            try
            {
                string ns = "{http://www.docusign.com/restapi}";
                XDocument doc = XDocument.Parse(xml);

                XElement root = doc.Element(ns + "errorDetails");
                if (root != null)
                {
                    return root.Element(ns + "message").Value;
                }
                return "Unknown error.";
                // return doc.ToString();
            }
            catch (Exception)
            {
                return xml;
            }
        }

        public string prettyPrintXml(string xml)
        {
            // print nicely formatted xml
            try
            {
                XDocument doc = XDocument.Parse(xml);
                return doc.ToString();
            }
            catch (Exception)
            {
                return xml;
            }
        }

        public void configureMultiPartFormDataRequest(HttpWebRequest request, string xmlBody, string docfullpath, string docName, string contentType)
        {
            // overwrite the default content-type header and set a boundary marker
            request.ContentType = "multipart/form-data; boundary=BOUNDARY";

            // start building the multipart request body
            string requestBodyStart = "\r\n\r\n--BOUNDARY\r\n" +
                "Content-Type: application/xml\r\n" +
                    "Content-Disposition: form-data\r\n" +
                    "\r\n" +
                    xmlBody + "\r\n\r\n--BOUNDARY\r\n" + 	// our xml formatted envelopeDefinition
                    "Content-Type: " + contentType + "\r\n" +
                    "Content-Disposition: file; filename=\"" + docName + "\"; documentId=1\r\n" +
                    "\r\n";
            string requestBodyEnd = "\r\n--BOUNDARY--\r\n\r\n";

            try
            {
                FileStream fileStream = File.OpenRead(docfullpath);

                // write the body of the request
                byte[] bodyStart = System.Text.Encoding.UTF8.GetBytes(requestBodyStart.ToString());
                byte[] bodyEnd = System.Text.Encoding.UTF8.GetBytes(requestBodyEnd.ToString());
                Stream dataStream = request.GetRequestStream();
                dataStream.Write(bodyStart, 0, requestBodyStart.ToString().Length);

                // Read the file contents and write them to the request stream.  We read in blocks of 4096 bytes
                byte[] buf = new byte[4096];
                int len;
                while ((len = fileStream.Read(buf, 0, 4096)) > 0)
                {
                    dataStream.Write(buf, 0, len);
                }
                dataStream.Write(bodyEnd, 0, requestBodyEnd.ToString().Length);
                dataStream.Close();
            }
            catch (WebException e)
            {
                using (WebResponse response = e.Response)
                {
                    HttpWebResponse httpResponse = (HttpWebResponse)response;
                    _log.Debug("[ERROR - DocuSignAPI] configureMultiPartFormDataRequest: " + httpResponse.StatusCode);

                    using (Stream data = response.GetResponseStream())
                    {
                        string text = new StreamReader(data).ReadToEnd();

                        throw new Exception(getErrorMessage(text));
                    }
                }
            }

        }

        internal void Logout(LoginDetails loginDetails)
        {
            WebClient webClient = new WebClient();
            webClient.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded");

            string token = webClient.UploadString(_url + "oauth2/revoke", string.Format("token={0}", loginDetails.AccessToken));
        }
    }
}